package br.com.massai.model.dao;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.hibernate.Criteria;
import org.hibernate.Session;

import br.com.massai.controller.filter.SessionControl;
import br.com.massai.model.repository.GenericRepository;

@SuppressWarnings({ "unchecked" , "rawtypes"})
public abstract class GenericDAO<T> implements GenericRepository<T>, Serializable {

	private static final long serialVersionUID = -1458477804788743416L;
	
	protected static Logger LOGGER = Logger.getLogger(GenericDAO.class.getName());
	private static final String LOG_INSERT = "INSERT";
	private static final String LOG_UPDATE = "UPDATE";
	private static final String LOG_DELETE = "DELETE";

	private Class<T> entity;
	private Session session;
	protected Map<String, Object> parameters;
	protected StringBuilder clauseWhere;

	@Inject @PersistenceContext protected EntityManager em;
	@Inject protected SessionControl sessionControl;

	public GenericDAO(Class<T> entity) {
		this.entity = entity;
	}
	
	private Session openSession(){
		if(session == null)
			session = (Session) em.getDelegate();
		return session;
	}

	public void criar(T entity) throws Exception {
		try {
			em.getTransaction().begin();
			em.persist(entity);
			em.getTransaction().commit();
			LOGGER.info("############### [ " + LOG_INSERT + " : "+ sessionControl.getUsuario().getLogin() +" ] TABELA: " + entity.getClass().getSimpleName());

		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Erro ao gravar registro.");
		}
	};

	public void excluir(T entity) throws Exception {
		try {
			em.getTransaction().begin();
//			T e = em.merge(entity);
//			em.remove(e);
			em.remove(entity);
			em.getTransaction().commit();
			LOGGER.info("############### [ " + LOG_DELETE + " : "+ sessionControl.getUsuario().getLogin() +" ] TABELA: " + entity.getClass().getSimpleName() + " >>> ID: " + getId(entity.getClass(), entity));
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Erro ao remover registro.");
		}
	};
	
	public T atualizar(T entity) throws Exception {
		try {
			em.getTransaction().begin();
			entity = em.merge(entity);
			em.getTransaction().commit();
			LOGGER.info("############### [ " + LOG_UPDATE + " : "+ sessionControl.getUsuario().getLogin() +" ] TABELA: " + entity.getClass().getSimpleName() + " >>> ID: " + getId(entity.getClass(), entity));
			return entity;

		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Erro ao atualizar registro.");
		}
	};
	
	public T obterPorId(Long id) throws Exception {
		try{
			return em.find(entity, id);
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Erro ao pesquisar registro.");
		}
	}
	
	public List<T> obterTodos() throws Exception {
		try {
			Criteria criteria = openSession().createCriteria(entity);
			return (List<T>) criteria.list();
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Erro ao pesquisar registros.");
		}
	}
	
    public List<T> obterPorExemplo(String namedQuery) throws Exception {
        List<T> result = null;
        try{
        	String queryStr = getNamedQuery(namedQuery, entity);

        	if(clauseWhere != null && clauseWhere.length() > 0){
        		String append = " WHERE 1=1 ";
        		if(queryStr.toUpperCase().contains("WHERE"))
        			append = " AND ";
        		queryStr = queryStr + append + clauseWhere;
        	}

            Query query = em.createQuery(queryStr);
            if(parameters != null && !parameters.isEmpty())
                for(Entry<String, Object> entry : parameters.entrySet())
                	query.setParameter(entry.getKey(), entry.getValue());

            result = query.getResultList();
 
        } catch (Exception e) {
        	LOGGER.warning("########### [" + entity.getName() + "] Erro ao tentar pesquisar.");
            e.printStackTrace();
            throw new Exception("Erro ao pesquisar registros.");
        }
        return result;
    }
    
    private String getNamedQuery(String namedQuery, Class c){
    	try{
    		NamedQueries namedQueries = (NamedQueries) c.getAnnotation(NamedQueries.class);
    		NamedQuery[] queriesArr = namedQueries.value();
    		for(NamedQuery n : queriesArr){
    			if(n.name().equals(namedQuery))
    				return n.query();
    		}
    		
    	} catch (Exception e){
    		e.printStackTrace();
    	}
    	return null;
    }
    
    private String getId(Class<?> clazz, Object obj) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
    	for (Field field : clazz.getDeclaredFields()) {
			if ((field.getAnnotation(Id.class)) != null) {
				Field privateField = clazz.getDeclaredField(field.getName());
				privateField.setAccessible(true);
				if (privateField.get(clazz.cast(obj)) != null) {
					return (String) field.getType().cast(privateField.get(clazz.cast(obj))).toString();
				} else {
					return null;
				}
			}
		}
		return null;
	}

	public Map<String, Object> getParameters() {
		return parameters;
	}

	public void setParameters(Map<String, Object> parameters) {
		this.parameters = parameters;
	}

	public StringBuilder getClauseWhere() {
		return clauseWhere;
	}

	public void setClauseWhere(StringBuilder clauseWhere) {
		this.clauseWhere = clauseWhere;
	}
}